/*******************************************************************************
 * Copyright (c) 2011 EclipseDevX.org and others.
 * 
 * All rights reserved. This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License v1.0 which
 * accompanies this distribution, and is available at:
 * 
 *     http://www.eclipse.org/legal/epl-v10.html
 *******************************************************************************/

package org.eclipsedevx.core.errors;

import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.resources.WorkspaceJob;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.GroupMarker;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.ColumnLabelProvider;
import org.eclipse.jface.viewers.ColumnViewer;
import org.eclipse.jface.viewers.DecorationOverlayIcon;
import org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider;
import org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider.IStyledLabelProvider;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.IColorProvider;
import org.eclipse.jface.viewers.IDecoration;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StyledString;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TableViewerColumn;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerColumn;
import org.eclipse.jface.viewers.ViewerFilter;
import org.eclipse.jface.viewers.ViewerSorter;
import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.Clipboard;
import org.eclipse.swt.dnd.TextTransfer;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IEditorReference;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.IMarkerResolution;
import org.eclipse.ui.IMarkerResolution2;
import org.eclipse.ui.IMemento;
import org.eclipse.ui.ISelectionListener;
import org.eclipse.ui.ISharedImages;
import org.eclipse.ui.IViewSite;
import org.eclipse.ui.IWorkbenchActionConstants;
import org.eclipse.ui.IWorkbenchCommandConstants;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.actions.ActionFactory;
import org.eclipse.ui.actions.SelectionProviderAction;
import org.eclipse.ui.ide.IDE;
import org.eclipse.ui.model.WorkbenchLabelProvider;
import org.eclipse.ui.part.ViewPart;
import org.eclipse.ui.progress.IWorkbenchSiteProgressService;
import org.eclipsedevx.core.DevXCorePlugin;
import org.eclipsedevx.core.errors.ext.AnnotationTypesExtManager;
import org.eclipsedevx.core.errors.ext.MarkersExtManager;

// TODO: sort java build path problems higher

// TODO: the marker descriptions can be empty for small transitory times

/**
 * 
 * 
 * @author dcarew
 */
public class ErrorsView extends ViewPart implements
		MarkersChangeService.MarkerChangeListener {
	private static Object REFRESH_MARKERS_JOB_FAMILY = new Object();

	private IMemento memento;

	protected TableViewer tableViewer;
	protected TableSorter tableSorter;
	
	private ErrorViewerFilter tableFilter;

	private IProject focusedProject;

	private FocusOnProjectAction focusOnProjectAction;

    private ShowInfosAction showInfosAction;
    
	private Clipboard clipboard;
	
    private CopyMarkerAction copyAction;
	
	public ErrorsView() {

	}

	@Override
	public void init(IViewSite site, IMemento memento) throws PartInitException {
		super.init(site, memento);

		this.memento = memento;

		IWorkbenchSiteProgressService progressService = (IWorkbenchSiteProgressService) getViewSite()
				.getAdapter(IWorkbenchSiteProgressService.class);

		if (progressService != null) {
			initProgressService(progressService);
		}
		
		getSite().getPage().addSelectionListener(new ISelectionListener() {
			public void selectionChanged(IWorkbenchPart part,
					ISelection selection) {
				if (part != ErrorsView.this) {
					focusOn(part, selection);
				}
			}
		});
	}

	@Override
	public void createPartControl(Composite parent) {
		clipboard = new Clipboard(parent.getDisplay());
		
		tableViewer = new TableViewer(parent, SWT.H_SCROLL | SWT.VIRTUAL
				| SWT.V_SCROLL | SWT.MULTI | SWT.FULL_SELECTION);
		tableViewer.setContentProvider(new ErrorViewTreeContentProvider());
		tableViewer.addDoubleClickListener(new IDoubleClickListener() {
			public void doubleClick(DoubleClickEvent event) {
				openSelectedMarker();
			}
		});
		tableViewer.addSelectionChangedListener(new ISelectionChangedListener() {
			public void selectionChanged(SelectionChangedEvent event) {
				ISelection selection = event.getSelection();
				if (selection instanceof IStructuredSelection) {
					updateStatusLine((IStructuredSelection) selection);
				}
			}
		});

		tableSorter = new TableSorter();
		tableViewer.setComparator(tableSorter);
		tableViewer.getTable().setSortDirection(SWT.UP);

		Table table = tableViewer.getTable();

		TableViewerColumn descriptionColumn = new TableViewerColumn(tableViewer, SWT.LEFT);
		descriptionColumn.setLabelProvider(new DescriptionLabelProvider());
		descriptionColumn.getColumn().setText("Description");
		descriptionColumn.getColumn().setWidth(500);
		descriptionColumn.getColumn().setResizable(true);
		enableSorting(descriptionColumn.getColumn(), 0);

		tableViewer.getTable().setSortColumn(descriptionColumn.getColumn());

		TableViewerColumn fileNameColumn = new TableViewerColumn(tableViewer, SWT.LEFT);
		fileNameColumn.setLabelProvider(new FileNameLabelProvider());
		fileNameColumn.getColumn().setText("Location");
		fileNameColumn.getColumn().setWidth(240);
		fileNameColumn.getColumn().setResizable(true);
		enableSorting(fileNameColumn.getColumn(), 1);

		TableViewerColumn typeColumn = new TableViewerColumn(tableViewer, SWT.LEFT);
		typeColumn.setLabelProvider(new TypeLabelProvider());
		typeColumn.getColumn().setText("Type");
		typeColumn.getColumn().setWidth(130);
		typeColumn.getColumn().setResizable(true);
		enableSorting(typeColumn.getColumn(), 2);

		restoreColumnWidths();

		table.setLayoutData(new GridData(GridData.FILL_BOTH));
		table.setFont(parent.getFont());

		table.setLinesVisible(true);
		table.setHeaderVisible(true);

		table.layout(true);

		getSite().setSelectionProvider(tableViewer);

		IToolBarManager toolbar = getViewSite().getActionBars().getToolBarManager();

		fillInToolbar(toolbar);
		registerContextMenu();
		
		tableFilter = new ErrorViewerFilter();

		updateFilters();

		startUpdateJob();

		MarkersChangeService.getService().addListener(this);

		focusOnActiveEditor();
	}

	@Override
	public void setFocus() {
		tableViewer.getTable().setFocus();
	}

	protected IMemento getMemento() {
		return memento;
	}

	protected TableViewer getViewer() {
		return tableViewer;
	}

	protected void showMarkers(final List<IMarker> markers) {
		for (int i = markers.size() - 1; i >= 0; i--) {
			if (!markers.get(i).exists()) {
				markers.remove(i);
			}
		}
		
		Display.getDefault().asyncExec(new Runnable() {
			public void run() {
				tableViewer.setInput(markers);
			}
		});
	}

	public void saveState(IMemento memento) {
		super.saveState(memento);

		memento.putString("partName", getPartName());

		StringBuilder builder = new StringBuilder();

		for (TableColumn column : tableViewer.getTable().getColumns()) {
			if (builder.length() > 0) {
				builder.append(";");
			}

			builder.append(Integer.toString(column.getWidth()));
		}

		memento.putString("columnWidths", builder.toString());
		
		if (focusOnProjectAction != null) {
			memento.putBoolean("focusOnProject", focusOnProjectAction.isChecked());
			memento.putBoolean("showInfos", showInfosAction.isChecked());
		}
	}

	protected String getStatusSummary(IMarker[] markers) {
		return getStatusSummary(Arrays.asList(markers));
	}

	protected String getStatusSummary(List<IMarker> markers) {
		return MarkersUtils.getInstance().summarizeMarkers(markers);
	}

	private void enableSorting(final TableColumn column, final int index) {
		column.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				tableSorter.setColumn(index);
				int dir = tableViewer.getTable().getSortDirection();
				if (tableViewer.getTable().getSortColumn() == column) {
					dir = dir == SWT.UP ? SWT.DOWN : SWT.UP;
				} else {
					dir = SWT.UP;
				}
				tableViewer.getTable().setSortDirection(dir);
				tableViewer.getTable().setSortColumn(column);
				tableViewer.refresh();
			}
		});
	}

	private void registerContextMenu() {
		copyAction = new CopyMarkerAction();
		
		getViewSite().getActionBars().setGlobalActionHandler(ActionFactory.COPY.getId(), copyAction);
		
		MenuManager mm = new MenuManager();
		mm.setRemoveAllWhenShown(true);
		mm.addMenuListener(new IMenuListener() {
			public void menuAboutToShow(IMenuManager mgr) {
				fillContextMenu(mgr);
			}
		});

		Viewer viewer = getViewer();

		Menu menu = mm.createContextMenu(viewer.getControl());
		viewer.getControl().setMenu(menu);

		// Register menu for extension.
		getSite().registerContextMenu(mm, viewer);
	}
	
	private void fillContextMenu(IMenuManager menuManager) {
		addActionsForSelection(menuManager);
		
		menuManager.add(new GroupMarker(IWorkbenchActionConstants.MB_ADDITIONS));
		
		if (menuManager.getItems().length > 1) {
			menuManager.add(new Separator());
		}
		
		menuManager.add(copyAction);
	}
	
	private void addActionsForSelection(IMenuManager menuManager) {
		IStructuredSelection selection = (IStructuredSelection) getViewer().getSelection();
		
		if (selection.size() == 1) {
			Object element = selection.getFirstElement();

			if (!(element instanceof IMarker)) {
				return;
			}

			final IMarker marker = (IMarker) element;

			IMarkerResolution[] resolutions = IDE.getMarkerHelpRegistry().getResolutions(marker);
			
			for (final IMarkerResolution resolution : resolutions) {
				Action action = new Action(escapeSpecialChars(resolution.getLabel())) {
					public void run() {
						resolution.run(marker);
					}
				};

				if (resolution instanceof IMarkerResolution2) {
					IMarkerResolution2 resolution2 = (IMarkerResolution2) resolution;
					Image image = resolution2.getImage();
					if (image != null) {
						action.setImageDescriptor(ImageDescriptor.createFromImage(image));
					}
				}

				menuManager.add(action);
			}
		}
	}

	private String escapeSpecialChars(String label) {
		if (label == null) {
			return "";
		}

		// handle OS X
		return label.trim().replace("@", "");
	}
	
	private void restoreColumnWidths() {
		if (memento != null) {
			String str = memento.getString("columnWidths");

			if (str != null) {
				String[] widths = str.split(";");
				TableColumn[] columns = tableViewer.getTable().getColumns();

				for (int i = 0; i < widths.length; i++) {
					if (columns.length > i) {
						try {
							columns[i].setWidth(Integer.parseInt(widths[i]));
						} catch (NumberFormatException nfe) {

						}
					}
				}
			}
		}
	}

	private void openSelectedMarker() {
		ISelection sel = tableViewer.getSelection();

		if (sel instanceof IStructuredSelection) {
			Object element = ((IStructuredSelection) sel).getFirstElement();

			if (element instanceof IMarker) {
				IMarker marker = (IMarker) element;

				if (marker.getResource() instanceof IFile) {
					try {
						IDE.openEditor(getViewSite().getPage(), marker);
					} catch (PartInitException e) {
					    ErrorDialog.openError(
					            getSite().getShell(),
					            "Error Opening Marker",
					            "Unable to open an editor for the given marker: " + e.getClass().getSimpleName(),
					            new Status(IStatus.ERROR, DevXCorePlugin.PLUGIN_ID, e.toString(), e));

						DevXCorePlugin.logError(e);
					}
				}
			}
		}
	}

	private void updateStatusLine(IStructuredSelection selection) {
		String message;

		if (selection == null || selection.size() == 0) {
			message = "";
		} else if (selection.size() == 1) {
			Object sel = selection.getFirstElement();

			if (sel instanceof IMarker) {
				IMarker marker = (IMarker) sel;

				message = marker.getAttribute(IMarker.MESSAGE, "");
			} else {
				message = "";
			}
		} else {
			List<IMarker> selMarkers = new ArrayList<IMarker>();

			for (Object obj : selection.toList()) {
				if (obj instanceof IMarker) {
					selMarkers.add((IMarker) obj);
				}
			}

			message = getStatusSummary((IMarker[]) selMarkers
					.toArray(new IMarker[selMarkers.size()]));
		}

		getViewSite().getActionBars().getStatusLineManager().setMessage(message);
	}
	
	protected void fillInToolbar(IToolBarManager toolbar) {
        focusOnProjectAction = new FocusOnProjectAction();
        showInfosAction = new ShowInfosAction();
        
		// toolbar.add(getProjectFocusAction());
        
		toolbar.add(focusOnProjectAction);
		toolbar.add(showInfosAction);
	}

	protected void initProgressService(IWorkbenchSiteProgressService progressService) {
		progressService.showBusyForFamily(ResourcesPlugin.FAMILY_MANUAL_BUILD);
		progressService.showBusyForFamily(ResourcesPlugin.FAMILY_AUTO_BUILD);

		progressService.showBusyForFamily(REFRESH_MARKERS_JOB_FAMILY);
	}

	private Job job;
	private boolean rescheduleJob;

	protected void startUpdateJob() {
		if (job != null) {
			rescheduleJob = true;
		} else {
			job = new WorkspaceJob("Refresh Problems") {
				@Override
				public boolean belongsTo(Object family) {
					return family == REFRESH_MARKERS_JOB_FAMILY;
				}

				@Override
				public IStatus runInWorkspace(IProgressMonitor monitor)
						throws CoreException {
					if (rescheduleJob) {
						rescheduleJob = false;
						schedule(1000);
					} else {
						monitor.beginTask("Refresh problems", IProgressMonitor.UNKNOWN);

						try {
							List<IMarker> markers = new ArrayList<IMarker>();

							for (String markerId : MarkersUtils.getInstance().getErrorsViewMarkerIds()) {
								IMarker[] marks = ResourcesPlugin.getWorkspace().getRoot().findMarkers(
									markerId, true, IResource.DEPTH_INFINITE);

								markers.addAll(Arrays.asList(marks));
							}

							showMarkers(markers);
						} catch (CoreException ce) {
							DevXCorePlugin.logError(ce);
						} finally {
							if (rescheduleJob) {
								rescheduleJob = false;
								schedule(1000);
							} else {
								job = null;
							}
						}

						monitor.done();
					}

					return Status.OK_STATUS;
				}
			};

			job.setRule(ResourcesPlugin.getWorkspace().getRoot());

			job.schedule(250);
		}
	}

	// implementation

	@Override
	public void dispose() {
		if (copyAction != null) {
			copyAction.dispose();
			copyAction = null;
		}
		
		if (clipboard != null) {
			clipboard.dispose();
			clipboard = null;
		}
		
		MarkersChangeService.getService().removeListener(this);

		super.dispose();
	}

	public void handleResourceChange() {
		startUpdateJob();
	}

	protected void updateContentDescription(List<IMarker> markers) {
		if (markers == null) {
			markers = new ArrayList<IMarker>();
		}

		String desc = getStatusSummary(markers);

		if (focusedProject != null && focusOnProjectAction.isChecked()) {
			desc = "Project " + focusedProject.getName() + ": " + desc;
		}

		setContentDescription(desc);
	}

	@SuppressWarnings("unused")
	private IEditorPart getEditorFor(IFile file) {
		IWorkbenchPage page = getViewSite().getPage();

		for (IEditorReference editorReference : page.getEditorReferences()) {
			try {
				IEditorInput editorInput = editorReference.getEditorInput();

				if (editorInput instanceof IFileEditorInput) {
					IFileEditorInput input = (IFileEditorInput) editorInput;

					if (file.equals(input.getFile())) {
						return editorReference.getEditor(true);
					}
				}
			} catch (PartInitException pie) {
				// ignore
			}
		}

		return null;
	}

	private void focusOnActiveEditor() {
		IEditorPart part = getViewSite().getPage().getActiveEditor();

		if (part == null) {
			focusOn(getViewSite().getPage().getActivePart(), null);
		} else {
			focusOn(part, null);
		}
	}

	private void focusOn(IWorkbenchPart part, ISelection selection) {
		Object sel = getSingleSelection(selection);

		// See if it's a resource
		if (sel instanceof IResource) {
			IResource resource = (IResource) sel;

			focusOn(resource.getProject());

			return;
		}

		// See if it can be adapted to a resource
		if (sel instanceof IAdaptable) {
			IAdaptable adaptable = (IAdaptable) sel;

			IResource resource = (IResource) adaptable.getAdapter(IResource.class);

			if (resource != null) {
				focusOn(resource.getProject());

				return;
			}
		}

		// See if the part is an editor.
		if (part instanceof IEditorPart) {
			IEditorPart editor = (IEditorPart) part;

			if (editor.getEditorInput() instanceof IFileEditorInput) {
				IFileEditorInput input = (IFileEditorInput) editor.getEditorInput();

				if (input.getFile() != null) {
					focusOn(input.getFile().getProject());
				}
			}
		}
	}

	private void focusOn(IProject project) {
		if (!safeEquals(focusedProject, project)) {
			focusedProject = project;

			updateFilters();
		}
	}

	private void updateFilters() {
		if (tableViewer.getFilters().length == 0) {
			tableViewer.setFilters(new ViewerFilter[] { tableFilter });
		}

		//tableFilter.setEnabled(filterContentsAction.isChecked());

		tableViewer.refresh();
	}

	private static class DescriptionLabelProvider extends ColumnLabelProvider {
		private Font boldFont;

		protected void initialize(ColumnViewer viewer, ViewerColumn column) {
			super.initialize(viewer, column);

			Font font = viewer.getControl().getFont();

			if (font != null) {
				FontData data = font.getFontData()[0];

				boldFont = new Font(font.getDevice(), data.getName(), data
						.getHeight(), SWT.BOLD);
			}
		}

		@Override
		public void dispose(ColumnViewer viewer, ViewerColumn column) {
			if (boldFont != null) {
				boldFont.dispose();
			}

			super.dispose(viewer, column);
		}

		@Override
		public Image getImage(Object element) {
			if (element instanceof IMarker) {
				IMarker marker = (IMarker) element;

				if (marker != null && marker.exists()) {
					Image image = null;

					try {
						image = WorkbenchLabelProvider
								.getDecoratingWorkbenchLabelProvider()
								.getImage(marker);
					} catch (Throwable t) {

					}

					if (image != null) {
						image = decorateImage(marker, image);

						return image;
					} else {
						return AnnotationTypesExtManager.getModel().getImageForMarker(marker);
					}
				}
			}

			return null;
		}

		private Image decorateImage(IMarker marker, Image image) {
			if (image == null) {
				return null;
			}

			if (IDE.getMarkerHelpRegistry().hasResolutions(marker)) {
				ImageDescriptor[] descriptors = new ImageDescriptor[5];

				descriptors[IDecoration.BOTTOM_RIGHT] = DevXCorePlugin
						.getBundledImageDescriptor("icons/ovr/contassist_ovr.gif");

				image = DevXCorePlugin.getPlugin().getImageManager()
						.createImage(new DecorationOverlayIcon(image, descriptors));
			}

			return image;
		}

		// @Override
		// public Font getFont(Object element) {
		// return null;
		// }

		@Override
		public String getText(Object element) {
			if (element instanceof IMarker) {
				IMarker marker = (IMarker) element;

				return marker.getAttribute(IMarker.MESSAGE, null);
			} else {
				return super.getText(element);
			}
		}

		@Override
		public Color getForeground(Object element) {
			return ErrorsView.getForeground(element);
		}

		@Override
		public Color getBackground(Object element) {
			return ErrorsView.getBackground(element);
		}
	}

	private static Color getForeground(Object element) {
		return null;
	}

	// private static Color BK_COLOR = null;

	private static Color getBackground(Object element) {
		// if (BK_COLOR == null) {
		// BK_COLOR = new Color(Display.getDefault(), 0xDD, 0xDD, 0xDD);
		// }

		return null;
	}

	private static class FileNameLabelProvider extends
			DelegatingStyledCellLabelProvider {

		public FileNameLabelProvider() {
			super(new FileNameStyledLabelProvider());
		}
	}

	private static class FileNameStyledLabelProvider extends LabelProvider
			implements IStyledLabelProvider, IColorProvider {
		public StyledString getStyledText(Object element) {
			StyledString str = new StyledString();

			if (element instanceof IMarker) {
				IMarker marker = (IMarker) element;

				IResource resource = marker.getResource();

				if (resource != null) {
					String name = resource.getName();

					if (resource instanceof IWorkspaceRoot) {
						name = "Workspace";
					}

					if (name == null) {
						name = "";
					}

					str.append(name);

					if (name.length() > 0) {
						int lineNumber = marker.getAttribute(IMarker.LINE_NUMBER, -1);

						if (lineNumber != -1) {
							String num = NumberFormat.getIntegerInstance().format(lineNumber);

							str.append(" [line " + num + "]", StyledString.DECORATIONS_STYLER);
						}
					}
				}
			}

			return str;
		}

		public Color getForeground(Object element) {
			return ErrorsView.getForeground(element);
		}

		public Color getBackground(Object element) {
			return ErrorsView.getBackground(element);
		}
	}

	static class TypeLabelProvider extends ColumnLabelProvider {
		@Override
		public String getText(Object element) {
			if (element instanceof IMarker) {
				return getMarkerTypeLabel((IMarker) element);
			} else {
				return "";
			}
		}

		@Override
		public Color getForeground(Object element) {
			return ErrorsView.getForeground(element);
		}

		@Override
		public Color getBackground(Object element) {
			return ErrorsView.getBackground(element);
		}
	}

	@SuppressWarnings("unused")
	private static String getMarkerToolTipText(IMarker marker) {
		StringBuilder builder = new StringBuilder();

		builder.append(marker.getAttribute(IMarker.MESSAGE, null));

		return builder.toString();
	}

	private static String getMarkerTypeLabel(IMarker marker) {
		try {
			String typeId = marker.getType();

			return MarkersExtManager.getInstance().getLabelforTypeId(typeId);
		} catch (CoreException ce) {
			return "";
		}
	}

	private static class ErrorViewTreeContentProvider implements
			ITreeContentProvider {

		private List<IMarker> markers = new ArrayList<IMarker>();

		private static final Object[] EMPTY_ARRAY = new Object[0];

		@SuppressWarnings("unchecked")
		public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
			markers = (List<IMarker>) newInput;
		}

		public Object getParent(Object element) {
			return null;
		}

		public Object[] getElements(Object inputElement) {
			List<Object> list = new ArrayList<Object>();

			list.addAll(markers);

			return list.toArray();
		}

		public Object[] getChildren(Object parentElement) {
			return EMPTY_ARRAY;
		}

		public boolean hasChildren(Object element) {
			return false;
		}

		public void dispose() {

		}
	}

	private static class TableSorter extends ViewerSorter {
		private static final int DESCENDING = 1;

		private int sortColumn;
		private int direction;

		public TableSorter() {
			direction = DESCENDING;
		}

		public void setColumn(int column) {
			if (sortColumn == column) {
				direction = -1 * direction;
			} else {
				sortColumn = column;

				direction = DESCENDING;
			}
		}

		@Override
		public int compare(Viewer viewer, Object e1, Object e2) {
			if (direction == DESCENDING) {
				return compareMarkers((IMarker) e1, (IMarker) e2);
			} else {
				return -1 * compareMarkers((IMarker) e1, (IMarker) e2);
			}
		}

		private final int compareMarkers(IMarker marker1, IMarker marker2) {
			// sort by severity
			// then by resource name
			// then by line number
			// then by problem description

			if (marker1 == null || marker2 == null || !marker1.exists()
					|| !marker2.exists()) {
				return 0;
			}

			int val = 0;

			if (sortColumn == 0) {
				val = compareSeverity(marker1, marker2);

				if (val != 0) {
					return val;
				}

				val = compareResourceName(marker1, marker2);

				if (val != 0) {
					return val;
				}

				val = compareLineNumber(marker1, marker2);

				if (val != 0) {
					return val;
				}

				return compareProblemDescription(marker1, marker2);
			} else if (sortColumn == 1) {
				val = compareResourceName(marker1, marker2);

				if (val != 0) {
					return val;
				}

				val = compareSeverity(marker1, marker2);

				if (val != 0) {
					return val;
				}

				val = compareLineNumber(marker1, marker2);

				if (val != 0) {
					return val;
				}

				return compareProblemDescription(marker1, marker2);
			} else {
				val = compareType(marker1, marker2);

				if (val != 0) {
					return val;
				}

				val = compareSeverity(marker1, marker2);

				if (val != 0) {
					return val;
				}

				val = compareResourceName(marker1, marker2);

				if (val != 0) {
					return val;
				}

				val = compareLineNumber(marker1, marker2);

				if (val != 0) {
					return val;
				}

				return compareProblemDescription(marker1, marker2);
			}
		}

		private static final int CAT_BUILDPATH = 10; // CategorizedProblem.CAT_BUILDPATH

		private final int compareSeverity(IMarker marker1, IMarker marker2) {
			int sev1 = marker1.getAttribute(IMarker.SEVERITY, IMarker.SEVERITY_INFO);
			int sev2 = marker2.getAttribute(IMarker.SEVERITY, IMarker.SEVERITY_INFO);

			if (sev1 == sev2 && sev1 == IMarker.SEVERITY_ERROR) {
				int category1 = marker1.getAttribute("categoryId", 0);
				int category2 = marker2.getAttribute("categoryId", 0);

				int compare1 = (category1 == CAT_BUILDPATH ? 0 : 1);
				int compare2 = (category2 == CAT_BUILDPATH ? 0 : 1);

				return compare1 - compare2;
			}

			// This order (sev2 - sev1) is deliberate.
			return sev2 - sev1;
		}

		private int compareResourceName(IMarker marker1, IMarker marker2) {
			String name1 = marker1.getResource().getName();
			String name2 = marker2.getResource().getName();

			return name1.compareToIgnoreCase(name2);
		}

		private int compareLineNumber(IMarker marker1, IMarker marker2) {
			int line1 = marker1.getAttribute(IMarker.LINE_NUMBER, 0);
			int line2 = marker2.getAttribute(IMarker.LINE_NUMBER, 0);

			return line1 - line2;
		}

		private int compareProblemDescription(IMarker marker1, IMarker marker2) {
			String desc1 = marker1.getAttribute(IMarker.MESSAGE, "");
			String desc2 = marker2.getAttribute(IMarker.MESSAGE, "");

			return desc1.compareToIgnoreCase(desc2);
		}

		private int compareType(IMarker marker1, IMarker marker2) {
			String desc1 = MarkersExtManager.getInstance()
					.getLabelForMarkerType(marker1);
			String desc2 = MarkersExtManager.getInstance()
					.getLabelForMarkerType(marker2);

			return desc1.compareToIgnoreCase(desc2);
		}
	}
	
	private class FocusOnProjectAction extends Action {
		public FocusOnProjectAction() {
			super("Filter on current project", AS_CHECK_BOX);

			setImageDescriptor(DevXCorePlugin.getBundledImageDescriptor("icons/core/filter_history.gif"));

			// restore state
			if (getMemento() != null) {
				Boolean val = getMemento().getBoolean("focusOnProject");

				if (val != null) {
					setChecked(val.booleanValue());
				}
			}
		}

		@Override
		public void run() {
			updateFilters();
		}
	}

	private class ShowInfosAction extends Action {
        public ShowInfosAction() {
            super("Show todos and informational messages", AS_CHECK_BOX);

            setImageDescriptor(DevXCorePlugin.getBundledImageDescriptor("icons/core/tasks_tsk.gif"));

            // restore state
            if (getMemento() != null) {
                Boolean val = getMemento().getBoolean("showInfos");

                if (val != null) {
                    setChecked(val.booleanValue());
                } else {
                    setChecked(true);
                }
            } else {
                setChecked(true);
            }
        }

        @Override
        public void run() {
            updateFilters();
        }
	}
	
	private class CopyMarkerAction extends SelectionProviderAction {
		public CopyMarkerAction() {
			super(tableViewer, "&Copy");
			
			setActionDefinitionId(IWorkbenchCommandConstants.EDIT_COPY);
			setImageDescriptor(PlatformUI.getWorkbench().getSharedImages()
					.getImageDescriptor(ISharedImages.IMG_TOOL_COPY));
		}
		
        @Override
        public void run() {
    		StringBuilder builder = new StringBuilder();
    		
        	for (Object obj : getStructuredSelection().toArray()) {
        		if (obj instanceof IMarker) {
        			IMarker marker = (IMarker)obj;
        			
        			if (builder.length() > 0) {
        				builder.append("\n");
        			}
        			
        			try {
						builder.append(marker.getAttribute(IMarker.MESSAGE));
					} catch (CoreException e) {
						
					}
        		}
        	}
        	
        	if (builder.length() > 0) {
        		copyToClipboard(builder.toString());
        	}
        }

		private void copyToClipboard(String str) {
			clipboard.setContents(
					new Object[] { str },
					new Transfer[] { TextTransfer.getInstance() });
		}
	}
	
	private static Object getSingleSelection(ISelection selection) {
		if (selection == null || selection.isEmpty()) {
			return null;
		}

		if (selection instanceof IStructuredSelection) {
			return ((IStructuredSelection) selection).getFirstElement();
		} else {
			return null;
		}
	}

	private static boolean safeEquals(Object o1, Object o2) {
		if (o1 == o2) {
			return true;
		}

		if (o1 == null || o2 == null) {
			return false;
		}

		return o1.equals(o2);
	}

	private class ErrorViewerFilter extends ViewerFilter {

	    public ErrorViewerFilter() {
	        
	    }
	    
		public Object[] filter(Viewer viewer, Object parent, Object[] elements) {
			preFilter();

			List<IMarker> results = new ArrayList<IMarker>();

			for (Object object : elements) {
				if (object instanceof IMarker) {
					if (select(viewer, parent, object)) {
						results.add((IMarker) object);
					}
				}
			}

			postFilter(results);

			return results.toArray();
		}

		protected void preFilter() {

		}

		private void postFilter(List<IMarker> results) {
			updateContentDescription(results);
		}

		@Override
		public boolean select(Viewer viewer, Object parentElement, Object element) {
			IMarker marker = (IMarker) element;

			if (!showInfosAction.isChecked()) {
			    if (marker.getAttribute(IMarker.SEVERITY, 0) == IMarker.SEVERITY_INFO) {
			        return false;
			    }
			}
			
			if (focusOnProjectAction.isChecked()) {
                IResource resource = marker.getResource();
                
                if (resource instanceof IWorkspaceRoot) {
                    return true; // markers on the root should always show
                }
                
	            if (focusedProject != null) {
	                return isChildOf(focusedProject, resource);
	            } else {
	                return false;
	            }
			}
			
			return true;
		}

		private boolean isChildOf(IProject project, IResource resource) {
			if (resource == null) {
				return false;
			} else if (resource instanceof IProject) {
				IProject other = (IProject) resource;

				return project.equals(other);
			} else {
				return isChildOf(project, resource.getParent());
			}
		}
	}

}
